/*
*  This file is part of ygg-brute
*  Copyright (c) 2020 ygg-brute authors
*  See LICENSE for licensing information
*/

struct EdwardsPoint {
    FieldElement x, y, z, t;
};

struct AffineNielsPoint {
    FieldElement y_plus_x, y_minus_x, xy2d;
};

DECLSPEC inline void point_identity(struct EdwardsPoint* p)
{
    fe_zero(p->x);
    fe_one(p->y);
    fe_one(p->z);
    fe_zero(p->t);
}

DECLSPEC inline void point_completed_to_extended(struct EdwardsPoint* p, const struct EdwardsPoint *r)
{
    fe_mul(p->x, r->x, r->t);
    fe_mul(p->y, r->y, r->z);
    fe_mul(p->z, r->z, r->t);
    fe_mul(p->t, r->x, r->y);
}

#define point_add_inplace(P, Q)              \
    do                                       \
    {                                        \
        FieldElement t0;                     \
        struct EdwardsPoint tp;              \
        struct EdwardsPoint *r = &tp;        \
                                             \
        fe_add(r->x, (P)->y, (P)->x);        \
        fe_sub(r->y, (P)->y, (P)->x);        \
        fe_mul(r->z, r->x, (Q)->y_plus_x);   \
        fe_mul(r->t, r->y, (Q)->y_minus_x);  \
        fe_sub(r->x, r->z, r->t);            \
        fe_add(r->y, r->z, r->t);            \
                                             \
        fe_mul(t0, (P)->t, (Q)->xy2d);       \
        fe_add(r->z, (P)->z, (P)->z);        \
        fe_sub(r->t, r->z, t0);              \
        fe_add(r->z, r->z, t0);              \
        point_completed_to_extended((P), r); \
    } while (0)

DECLSPEC inline void point_to_montgomery_bytes(const struct EdwardsPoint* p, uint8_t bytes[32])
{
    FieldElement u, v;
    fe_sub(u, p->z, p->y);
    fe_invert(u, u);
    fe_add(v, p->z, p->y);
    fe_mul(u, u, v);
    fe_to_bytes(bytes, u);
}

DECLSPEC inline void point_double_inner(struct EdwardsPoint* r, const struct EdwardsPoint* p)
{
    fe_square(r->x, p->x);
    fe_square(r->t, p->y);
    fe_add(r->y, r->t, r->x);
    fe_sub(r->z, r->t, r->x);
    fe_add(r->x, p->x, p->y);
    fe_square(r->x, r->x);
    fe_sub(r->x, r->x, r->y);

    // fe_square2(r->t, p->z);
    fe_mul(r->t, p->z, p->z);
    fe_add(r->t, r->t, r->t);

    fe_sub(r->t, r->t, r->z);
}

DECLSPEC inline void point_double(struct EdwardsPoint* r, const struct EdwardsPoint* p)
{
    struct EdwardsPoint tr;
    point_double_inner(&tr, p);
    point_completed_to_extended(r, &tr);
}

DECLSPEC inline void point_double_inplace(struct EdwardsPoint* p)
{
    struct EdwardsPoint tr;
    point_double_inner(&tr, p);
    point_completed_to_extended(p, &tr);
}

DECLSPEC inline void point_mul16_inplace(struct EdwardsPoint* p)
{
    struct EdwardsPoint tp;
    struct EdwardsPoint* r = &tp;
    for(int i = 0; i < 4; ++i) {
        point_double_inner(r, p);
        fe_mul(p->x, r->x, r->t);
        fe_mul(p->y, r->y, r->z);
        fe_mul(p->z, r->z, r->t);
    }

    fe_mul(p->t, r->x, r->y);
}
